#include "RayTracer.h"
#include <Windows.h>

RayTracer::RayTracer(): 
	m_pScenceParser(NULL),
	m_pImage(0),
	WEIGHTDEDUCE(0.97f),
	m_t_min(500*FLT_EPSILON),
	m_t_max(FLT_MAX)
{
}

RayTracer::~RayTracer()
{
	if ( m_pImage )
		delete m_pImage;
	if ( m_pScenceParser )
		delete m_pScenceParser;
}

void RayTracer::setScene(const char *input_file)
{
	m_pScenceParser = new SceneParser(input_file);
	const RenderConfig& config = m_pScenceParser->getRenderConfig();
	m_width = config.width;
	m_height = config.height;
	m_sampleNum = config.sample;
	m_max_bounces = config.max_bounces;
	m_min_wight = config.minWeight;
	m_pImage = new Image(m_width, m_height);

	printf("scene[%s] size[%d,%d]\tsample[%d]\tbounces[%d]\tweight[%f]\r\n", 
		input_file, m_width, m_height, m_sampleNum,m_max_bounces,m_min_wight);
}

bool RayTracer::render()
{
	float startTime = timeGetTime();
	printf("\nstep 3: rendering...\n");
	Camera *camera = m_pScenceParser->getCamera();
	int x,y;
	float fx,fy;
	for (x=0;x<m_width;x++)
	{
		printf("rendering %3.f%%\r",100.f*x/m_width);
		for (y=0;y<m_height;y++)
		{
			int center = m_sampleNum/2;
			float step = 1.f/m_sampleNum;
			Vec3f totalColor(0.f,0.f,0.f);
			for(int kx=0;kx<m_sampleNum;kx++)
			{
				for(int ky=0;ky<m_sampleNum;ky++)
				{
					fx = x + 0.5f + (kx-center)*step;
					fy = y + 0.5f + (ky-center)*step;
					fx /= m_width; fy /= m_height;
					Ray ray = camera->generateRay(fx,fy);
					Vec3f color = traceRay(&ray,m_t_min,m_t_max,0,1.0f,1.f);
					totalColor += color;
				}
			}
			m_pImage->SetPixel(x, m_height-y-1, totalColor*step*step);	
		}
	}
	printf("\n%.3fs\n",(timeGetTime()-startTime)/1000.f);
	m_pImage->SaveTGA( m_pScenceParser->getRenderConfig().output );
	return true;
}

Vec3f RayTracer::traceRay(Ray *pRay, float t_min, float t_max, int bounces, 
						  float weight, float indexOfRefraction)const
{
	if (bounces>m_max_bounces || weight<m_min_wight)
		return Vec3f();

	Hit hit;
	SceneTree *group = m_pScenceParser->getSceneTree();
	if ( group->intersect(pRay,&hit,t_min, t_max) )
	{
		Vec3f color;
		Vec3f::AddMul(color,m_pScenceParser->getAmbientLight(),hit.getAmbientColor());
		int lights = m_pScenceParser->getNumLights();
		bool anyLighting = false;
		while(--lights>=0)
		{
			Vec3f lightColor,lightDir,addColor;
			float distanceToLight;
			Light *pLight = m_pScenceParser->getLight(lights);
			pLight->getIllumination(hit.getIntersectionPoint(),lightDir,
				lightColor,distanceToLight);

			Ray shadowRay(lightDir,hit.getIntersectionPoint());
			if ( !group->intersectShadowRay(&shadowRay,500*FLT_EPSILON,distanceToLight) ){
				addColor = hit.Shade(*pRay,lightDir,lightColor);
				color += addColor;
				anyLighting = true;
			}
		}
		if (hit.getMaterial()->isMirror())
		{
			Vec3f mirrDir = getMirrorDir(pRay->getDirection()*-1.f,hit.getNormal());
			Ray mirrRay(mirrDir, hit.getIntersectionPoint());
			Vec3f mirrColor;
			Vec3f::AddMul(mirrColor, hit.getMaterial()->getReflectiveColor(), 
				traceRay(&mirrRay,t_min,t_max,bounces+1,weight*WEIGHTDEDUCE,1));
			color += mirrColor;
		}
		if (hit.getMaterial()->isTransparent())
		{
			float newIndexOfRefraction;
			Vec3f normal = hit.getNormal();
			if( pRay->getDirection().Dot3(hit.getNormal()) > FLT_EPSILON ) // inside
			{
				newIndexOfRefraction = 1.f;
				normal = normal*-1.f;
			}
			else
				newIndexOfRefraction = hit.getMaterial()->getIndexOfRefraction();
			Vec3f transmitDir;
			if (getTransparentDir(normal,pRay->getDirection()*-1.f,
				indexOfRefraction,newIndexOfRefraction,transmitDir))
			{
				Ray transparentRay(transmitDir, hit.getIntersectionPoint());
				Vec3f transmitColor = traceRay(&transparentRay,t_min,t_max,bounces+1,
					weight*WEIGHTDEDUCE,newIndexOfRefraction);
				Vec3f transparentColor;
				Vec3f::AddMul(transparentColor, hit.getMaterial()->getTransparentColor(),transmitColor);
				color += transparentColor;
			}
		 }
		return color*weight;
	}
	return m_pScenceParser->getBackgroundColor()*weight;
}

Vec3f RayTracer::getMirrorDir(const Vec3f L, const Vec3f N)const
{
	return N*(2*N.Dot3(L))-L;
}

bool RayTracer::getTransparentDir(const Vec3f &normal, const Vec3f &incoming,
					   float index_i, float index_t, Vec3f &transDir )const
{
	float nr = index_i / index_t;
	// 		if ( nr > 1.0f + FLT_EPSILON || nr < 1.0 - FLT_EPSILON )
	// 			printf("a");
	float dot = normal.Dot3(incoming);
	transDir = normal * (nr*dot-sqrt(1.f-nr*nr*(1.f-dot*dot))) - (incoming*nr);
	// 		if ( (transDir+incoming).isZero() == 0 )
	// 			printf("b");
	transDir.Normalize();
	return true;
}